"""
================================================================================
                     ------------utf-8--------------
================================================================================
@Author: rfdsg
@Create Time: 2024/1/5 - 19:10
@Description:
@Attention:
"""
from typing import Optional, Union
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, mean_absolute_error
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.stattools import adfuller, kpss
from sklearn.ensemble import IsolationForest
from statsmodels.tsa.arima.model import ARIMA
from pmdarima.arima import auto_arima


'''

拖尾（Tail）： 拖尾指的是残差序列中的自相关函数（ACF）或偏自相关函数（PACF）在一定滞后阶数之后仍然不断衰减的情况。
换句话说，如果残差序列的自相关或偏自相关在一定的滞后阶数之后仍然显著地不为零，那么我们可以说该序列存在拖尾。
拖尾的存在表明模型中可能存在一些未能捕捉到的结构或信息，这可能会影响模型的预测性能。

截尾（Truncation）： 截尾则是指残差序列中的自相关函数或偏自相关函数在一定滞后阶数之后迅速衰减并趋向于零的情况。
如果残差序列的自相关或偏自相关在一定的滞后阶数之后就显著地趋于零，那么我们可以说该序列存在截尾。
截尾的存在通常是理想的情况，表示模型能够很好地捕捉数据的结构，残差序列中的信息已经基本被模型拟合了。
'''


def plot_time_series(data):
    """

    Returns:
        None
    Information
    ---------------------
        画出原始图
    """
    data.net_base()
    plt.title('Time Series Plot')
    plt.show()


def decompose_time_series(data, period: int = 20):
    """

    Returns:
        None
    Information
    ---------------------------
    原始时间序列图：
        这是你输入的原始时间序列数据。通过观察这个图，你可以了解时间序列的整体趋势和模式。
    趋势成分图：
        这个图显示了数据中的趋势成分。趋势是数据长期上升或下降的趋势。观察这个图可以帮助你了解时间序列的长期趋势。
    季节性成分图：
        季节性成分显示了数据中重复的模式，通常与特定的季节或周期有关。观察这个图可以帮助你发现数据是否在特定时间段内存在重复的模式。
    残差成分图：
        残差是原始数据与趋势和季节性成分相结合后的部分，即原始数据减去趋势和季节性。残差图可以帮助你判断模型是否成功地捕捉了数据中的所有信息。
        如果残差图中存在结构或模式，可能意味着模型还可以进一步改进。
    """
    decomposition = seasonal_decompose(data, model='additive', period=period)
    decomposition.plot()
    plt.suptitle('Time Series Decomposition')
    # plt.savefig('seasonal.jpg', dpi=3000)
    plt.show()
    return decomposition.resid


def acf(data, lags=20):
    """

    Args:
        data:
        lags: 滞后阶数=20

    Returns:
        None
    Information
    -------------------------
        它表示时间序列不同滞后期之间的整体相关性，即考虑了所有中间时刻的数据对当前时刻的影响。
        使用 plot_acf() 函数绘制自相关图。
        参数 lags=20 指定自相关图的最大滞后阶数。
    """
    plot_acf(data, lags=lags)
    plt.title('Autocorrelation Plot')
    plt.show()


def pacf(data, lags=20):
    """
    Args:
        data: 数据
        lags: 滞后阶数=20

    Returns:
        None
    Information
    --------------------------
        它表示的是不同滞后期之间的直接相关性，即排除了中间时刻的影响，专注于某个特定滞后期与当前时刻之间的关系。
        使用 plot_pacf() 函数绘制偏自相关图。
        参数 lags=20 指定偏自相关图的最大滞后阶数。
    """
    plot_pacf(data, lags=lags)
    plt.title('Partial Autocorrelation Plot')
    plt.show()


def adf_test(data):
    """

    Returns:
        None
    Information
    -------------------------
    ADF Statistic（ADF统计量）：
        - ADF统计量是用于判断时间序列数据是否具有单位根的检验统计量。如果ADF统计量小于临界值（也就是第三个数据）我们会拒绝原假设，认为时间序列是平稳的。
    p-value：
        - p-value是用于判断在给定的统计模型下，观察到的统计量或更极端结果的概率。在ADF检验中，如果p-value小于显著性水平（通常为0.05），
        与其他两数据无关，我们可以拒绝原假设，得到时间序列是平稳的。
    Critical Values（临界值）：
        - 1%、5%、10%的临界值.它们与等值的置信度对应
        - 这些值是用于比较ADF统计量，判断是否拒绝原假设的阈值。如果ADF统计量小于这些临界值，我们可能会拒绝原假设，得到时间序列是平稳的。
    注意，我们需要平稳序列
    """
    result = adfuller(data.squeeze())
    print(f'ADF 统计量: {result[0]}')
    print(f'p-value: {result[1]}')
    print('临界值:')
    for key, value in result[4].items():
        print(f'   {key}: {value}')


def white_noise(time_series_data, lags=None, alpha=0.05):
    """
    执行白噪声检验，使用 Ljung-Box 检验。
    如果通过检验，则为白噪声，都大于则接受白噪声，否则有自相关性
    注意，我们不需要白噪声
    Args:
        time_series_data: 时间序列数据
        lags: 检验的滞后阶数，默认为 None，表示自动选择滞后阶数
        alpha: 显著性水平，默认为 0.05

    Returns:
        result: 检验结果，True 表示时间序列是白噪声，False 表示时间序列不是白噪声。
        只有当所有 p 值都大于显著性水平时，我们才能得出时间序列是白噪声的结论。
    """
    # 执行 Ljung-Box 检验
    test_result = acorr_ljungbox(time_series_data, lags=lags)
    # 提取 p 值列
    p_value_str = test_result['lb_pvalue'].values
    # 检查 p 值是否小于显著性水平
    result = all(p > alpha for p in p_value_str)
    print(result)
    return result


def identify_best_arima_model(time_series_data, seasonal=False, i_not=True, season_len: Optional[int] = 5) -> ARIMA:
    """
    Args:
        season_len:
        i_not:是否使用stepwise，即加速搜索，默认为启用
        time_series_data (pd.Series): 时间序列数据。
        seasonal (bool): 是否考虑季节性。默认为 False。

    Returns:
        model:
            .order可获得:
                - 最佳 ARIMA 模型的参数 (p, d, q)。
                - 模型中的参数 p 表示自回归的阶数，即考虑的滞后项的数量。
                - 模型中的参数 d 表示差分的阶数，即进行了多少次差分操作。
                - 模型中的参数 q 表示移动平均的阶数，即考虑的滞后误差的数量。
Information
--------------------------------
    根据 AIC 准则识别时间序列数据的最佳 ARIMA 模型。
         summary():
            获取各项数据:
                - Dep. Variable（因变量）: 表示分析的因变量是哪一个时间序列。
                - No. Observations（观测数量）: 表示观测值的数量。
                - Model（模型）: 表示使用的 SARIMAX 模型的参数，例如 "(5, 0, 0)" 表示自回归阶数为 5，移动平均阶数为 0，差分阶数为 0。
                - Log Likelihood（对数似然）: 模型的对数似然值，用于评估模型的拟合程度。值越高表示模型拟合得越好。
                - AIC, BIC, HQIC（信息准则）: 用于比较不同模型之间的拟合优度，AIC、BIC、HQIC 值越低表示模型拟合得越好。
                - Covariance Type（协方差类型）: 模型拟合过程中所采用的协方差矩阵的计算方法。
                - coef（系数）: 各个自回归（AR）和外生变量（Exogenous Variables）的系数估计值。（近似斜率）
                - std err（标准误差）: 系数的标准误差，用于估计系数的可靠性。
                - z（z 统计量）: 系数的 z 统计量，用于检验系数是否显著不等于零。
                - P>|z|（p 值）: 系数的 p 值，表示系数是否显著不等于零的概率。
                - sigma2（方差）: 模型残差的方差估计值。
                - Ljung-Box (L1) (Q)（Ljung-Box 统计量）: 用于检验残差序列是否为白噪声的统计量，"(L1)" 表示一阶滞后。
                - Prob(Q)（p 值）: Ljung-Box 统计量对应的 p 值，表示残差序列是否为白噪声的概率。
                - Heteroskedasticity (H)（异方差性）: 检验残差序列是否存在异方差性的统计量。
                - Prob(H)（p 值）: 异方差性检验的 p 值，表示残差序列是否存在异方差性的概率。
                - Jarque-Bera (JB)：Jarque-Bera 统计量用于检验残差序列的偏度和峰度是否符合正态分布。
                - Prob(JB) 表示 Jarque-Bera 统计量对应的 p 值。用法与Prob(Q)相同。
                - Skew：偏度是衡量残差序列偏离正态分布的程度。如果残差序列的偏度值较大，则表明序列存在明显的偏斜。
                - Kurtosis：峰度是衡量残差序列尖峰性的统计量。如果残差序列的峰度值较大，则表明序列具有较尖的峰度。
                - coef（系数）: 这一列显示了模型中每个自变量的系数估计值。在这个例子中，"intercept" 是截距项的系数，而 "ar.L1"、"ar.L2" 等是自回归模型的滞后阶数的系数。
                - std err（标准误差）: 这一列显示了每个系数估计值的标准误差，用于衡量估计值的不确定性。标准误差越大，表示估计值的可信度越低。
                - z（z 统计量）: 这一列显示了每个系数的 z 统计量，用于检验该系数是否显著不等于零。z 统计量是系数估计值除以其标准误差的结果。
                - P>|z|（p 值）: 这一列显示了 z 统计量对应的 p 值，表示系数是否显著不等于零的概率。通常，如果 p 值小于显著性水平（通常是 0.05），则可以拒绝系数等于零的假设，即认为该系数显著不为零。
                - [0.025, 0.975]: 这一列显示了系数的置信区间，通常取 95% 置信水平。置信区间表示了对系数真值的估计范围，95% 置信区间表示有 95% 的概率真值落在这个区间内。
                - sigma2（方差）: 这一项显示了模型残差的方差估计值，用于衡量模型拟合数据的好坏。较小的残差方差表示模型能够较好地解释数据的变化。



    """
    # 自动确定 ARMA 模型的最佳参数
    model = auto_arima(time_series_data, start_p=0, start_q=0, d=0, max_p=3, max_q=3,
                       seasonal=seasonal, m=season_len, stepwise=i_not, n_jobs=15, suppress_warnings=True, )
    # suppress_warnings = True: 表示忽略警告信息。
    order = model.order
    print(order)
    return model


def in_sample_resi(model, data_train: pd.Series, row=True, acf_=True, pacf_=True):
    residuals = model.resid()
    forecast = model.predict_in_sample()
    # 误差计算
    mae = mean_absolute_error(data_train, forecast)
    rmse = np.sqrt(mean_squared_error(data_train, forecast))

    print(f"\n📈 in_sample预测评估:")
    print(f"MAE:  {mae:.4f}")
    print(f"RMSE: {rmse:.4f}")
    if row:
        # 计算残差序列的均值和标准差
        residuals_mean = np.mean(residuals)
        residuals_std = np.std(residuals)
        # 计算标准化残差
        standardized_residuals = (residuals - residuals_mean) / residuals_std
        # 绘制模型拟合的残差序列图
        plt.figure(figsize=(10, 4))
        plt.plot(standardized_residuals)
        plt.title('Normal_Residuals of train_data_ARMA Model')
        plt.xlabel('Time')
        plt.ylabel('Residuals')
        plt.show()
    if acf_:
        # 绘制残差序列的自相关图和偏自相关图
        plot_acf(residuals, lags=20)
        plt.xlim(0, 20)  # 设置横轴范围为 0 到 20
        plt.title('ACF of train_data_Residuals')
        plt.show()
    if pacf_:
        plot_pacf(residuals, lags=20)
        plt.xlim(0, 20)
        plt.title('PACF of train_data_Residuals')
        plt.show()

    return residuals


def out_sample_resi(
        data_test: pd.Series,
        model: ARIMA,
):
    """
    拟合 ARIMA 模型并对未来进行预测，评估预测效果并绘图。

    参数：
    - data_test: 测试集真实值
    - model: 拟合的 ARIMA 模型

    返回：

    """

    # 预测未来
    forecast = model.predict(n_periods=len(data_test))
    forecast.index = data_test.index

    # 误差计算
    mae = mean_absolute_error(data_test, forecast)
    rmse = np.sqrt(mean_squared_error(data_test, forecast))

    print(f"\n📈 out_sample预测评估:")
    print(f"MAE:  {mae:.4f}")
    print(f"RMSE: {rmse:.4f}")

    # 可视化对比
    plt.figure(figsize=(12, 5))
    plt.plot(data_test, label="true")
    plt.plot(forecast, label="predict", linestyle="--")
    plt.legend()
    plt.title("ARIMA predict vs true")
    plt.grid(True)
    plt.show()

    # 残差图
    residuals = data_test - forecast
    residuals_mean = np.mean(residuals)
    residuals_std = np.std(residuals)
    # 计算标准化残差
    standardized_residuals = (residuals - residuals_mean) / residuals_std
    plt.figure(figsize=(10, 4))
    plt.plot(standardized_residuals)
    plt.axhline(0, linestyle='--', color='red')
    plt.title("data_test_redi")
    plt.grid(True)
    plt.show()

    # 残差的 ACF 和 PACF
    plot_acf(residuals, lags=20)
    plt.title("ACF of test_data_Residuals")
    plt.show()

    plot_pacf(residuals, lags=20)
    plt.title("PACF of test_data_Residuals")
    plt.show()

    return residuals



def kpss_test(data):
    """

    Returns:
        None
Information
--------------------
    使用 kpss() 函数进行 KPSS 检验，判断趋势平稳性
    """
    result = kpss(data.squeeze(), regression='c')
    print(f'KPSS Statistic: {result[0]}')
    print(f'p-value: {result[1]}')
    print('Critical Values:')
    for key, value in result[3].items():
        print(f'   {key}: {value}')


def isolation_forest(data):
    """

    Returns:

Information
-----------------------
    使用孤立森林（Isolation Forest）进行异常检测，并将异常标记在图上。
    参数 contamination=0.05 指定异常值的比例。
    """
    model = IsolationForest(contamination=0.05)
    data['anomaly'] = model.fit_predict(data.values.reshape(-1, 1))
    data.net_base()
    plt.title('Isolation Forest Anomaly Detection')
    plt.show()


def descriptive_statistics(data):
    """
    计算并打印描述性统计量，包括偏度和峰度。
    Returns:
        None
    """
    statistics = data.describe()
    skewness = data.skew()
    kurt = data.kurtosis()
    # 将英文列名翻译成中文
    chinese_columns = {
        'count': '样本数',
        'mean': '均值',
        'std': '标准差',
        'min': '最小值',
        '25%': '25%分位数',
        '50%': '中位数',
        '75%': '75%分位数',
        'max': '最大值'
    }

    statistics = statistics.rename(columns=chinese_columns)
    statistics.loc['偏度'] = skewness
    statistics.loc['峰度'] = kurt

    print(statistics)
    return statistics



def ir(data: np.ndarray) -> int:
    """
    Args:
        data(int): 数据

    Returns:
        int: IR值

    Information:
    IR值的各种用法

     IR=np.mean(IC) ÷ np.std(IC)

    其中，IC为组合的收益率与整个市场（一般指指数）的平均收益率的相关系数。

    IR=TD/TE

    其中，TD表示资产跟踪偏离度的样本均值，TE为资产的跟踪误差。

    IR=(投资组合平均收益率-业绩比较基准平均收益率)/跟踪误差

    IR=α/ω
    其中，α为组合的超额收益，ω为主动风险。分子α为真实预期收益率与定价模型所计算出的收益率的差，α可以由风险溢价暗示。

    需要注意的是，不同的计算公式可能适用于不同的情况，投资者应根据自身的需求和情况选择合适的计算方法。
    """
    return np.mean(data) / np.std(data)
